package ru.neoflex.jedit.plugin.core;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.set.CompositeSet;
import org.apache.commons.io.FileUtils;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import ru.neoflex.jedit.plugin.utils.Assert;
import ru.neoflex.jedit.plugin.utils.XmlUtils;

import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.util.*;

/**
 * Индекс для property файлов
 */
public class PropertiesIndex {
    private final Map<String, Properties> files = new HashMap<String, Properties>();
    private final List<BundleArtefact> bundles = new ArrayList<BundleArtefact>();

    public Map<String, Properties> getPropertiesByBundleName(String bundleName) {
        Assert.notBlank(bundleName);
        Assert.isTrue(hasBundle(bundleName));

        List<BundleArtefact> bundleArtefacts = getBundles(bundleName);
        Assert.notNull(bundleArtefacts);
        Assert.isTrue(bundleArtefacts.size() > 0);

        Map<String, Properties> result = new HashMap<String, Properties>();
        for (BundleArtefact bundleArtefact : bundleArtefacts) {
            String basename = bundleArtefact.getBasename().replaceAll(".", File.separator);
            String var = bundleArtefact.getVar() + ".properties";

            for (String filePath : files.keySet()) {
                if (filePath.endsWith(basename + var)) {
                    Properties props = files.get(filePath);
                    result.put(filePath, props);
                }
            }
        }
        return result;
    }

    public List<BundleArtefact> getBundles(String bundleName) {
        Assert.notBlank(bundleName);
        Assert.isTrue(hasBundle(bundleName));

        List<BundleArtefact> result = new ArrayList<BundleArtefact>();
        for (BundleArtefact bundleArtefact : bundles) {
            if (bundleArtefact.getVar().equals(bundleName)) {
                result.add(bundleArtefact);
            }
        }

        return result;
    }

    public boolean hasBundle(String bundleName) {
        Assert.notBlank(bundleName);

        for (BundleArtefact bundleArtefact : bundles) {
            if (bundleArtefact.getVar().equals(bundleName)) {
                return true;
            }
        }

        return false;
    }

    public void addPropertyToFile(String file, String propertyName, String propertyValue) throws IOException {
        Properties props = new Properties();
        props.load(new FileReader(file));
        props.put(propertyName, propertyValue);
        props.save(new FileOutputStream(new File(file)), "Updated by Neoflex plugin");
    }

    public Map<String/*file*/, String/*property value*/> getPropertyValueFromBundle(String bundleName, String propertyName) {
        Map<String, String> result = new HashMap<String, String>();
        for (BundleArtefact bundle : getBundles(bundleName)) {
            String basename = bundle.getBasename().replaceAll(".", File.separator);
            String var = bundle.getVar() + ".properties";

            for (String filePath : files.keySet()) {
                if (filePath.endsWith(basename + var)) {
                    Properties props = files.get(filePath);
                    if (props.containsKey(propertyName)) {
                        result.put(filePath, props.getProperty(propertyName));
                    }
                }
            }
        }
        return result;
    }

    public BundleHint getBundleHint(String bundleName, String propertyName) {
        BundleHint result = new BundleHint();
        for (BundleArtefact bundle : bundles) {
            if (bundle.getVar().equals(bundleName) &&
                    files.get(bundle.basename).containsKey(propertyName)) {

                BundleEntry bundleEntry = new BundleEntry();
                bundleEntry.setFilename(bundle.getBasename());
                bundleEntry.setValue(files.get(bundle.getBasename()).getProperty(propertyName));

                result.getEntries().add(bundleEntry);
            }
        }
        return result;
    }

    public void processDirectory(String rootPath) throws IOException, SAXException, ParserConfigurationException {
        Assert.notBlank(rootPath);

        File root = new File(rootPath);
        Assert.isTrue(root.exists());
        Assert.isTrue(root.isDirectory());

        Collection<File> files = FileUtils.listFiles(root, new String[]{"properties", "props", "xhtml"}, true);
        for (File f : files) {
            if (f.getName().endsWith(".properties") || f.getName().endsWith(".props")) {
                processFile(f.getAbsolutePath());
            }
            if (f.getName().endsWith(".xhtml")) {
                processXhtml(f.getAbsolutePath());
            }
        }
    }

    public void processXhtml(String filePath) throws ParserConfigurationException, IOException, SAXException {
        Assert.notNull(filePath);
        System.out.println("Processing: " + filePath);

        File file = new File(filePath);
        Assert.isTrue(file.exists());
        Assert.isTrue(!file.isDirectory());
        Assert.isTrue(file.getName().endsWith(".xhtml"));

        Set<BundleArtefact> bundlesToRemove = new CompositeSet();
        for (BundleArtefact bundle : bundles) {
            if (bundle.getSourceFile().equals(filePath)) {
                bundlesToRemove.add(bundle);
            }
        }
        CollectionUtils.removeAll(bundles, bundlesToRemove);

        Document doc = null;
        try {
            doc = XmlUtils.parseXml(file);
        } catch (SAXParseException e) {
            System.out.println("File " + filePath + " contains errors");
            return;
        }
        Assert.notNull(doc);

        List<Map<String, String>> result = XmlUtils.getTagsByName(doc, "f:loadBundle");
        if (result.size() > 0) {
            for (Map<String, String> item : result) {
                String basename = item.get("basename");
                String var = item.get("var");

                BundleArtefact bundleArtefact = new BundleArtefact();
                bundleArtefact.setSourceFile(filePath);
                bundleArtefact.setVar(var);
                bundleArtefact.setBasename(basename);

                bundles.add(bundleArtefact);
            }
        }
    }

    public void processFile(String filePath) throws IOException {
        Assert.notNull(filePath);

        File file = new File(filePath);
        Assert.isTrue(file.exists());
        Assert.isTrue(!file.isDirectory());
        Assert.isTrue(file.getName().endsWith(".properties") || file.getName().endsWith(".props"));

        Properties props = new Properties();
        props.load(new FileReader(file));

        files.remove(filePath);
        files.put(filePath, props);
    }

    public void clear() {
        files.clear();
        bundles.clear();
    }

    public static class BundleArtefact extends Artefact {
        private String var;
        private String basename;

        public String getVar() {
            return var;
        }

        public void setVar(String var) {
            this.var = var;
        }

        public String getBasename() {
            return basename;
        }

        public void setBasename(String basename) {
            this.basename = basename;
        }
    }

    public static class BundleHint {
        private final List<BundleEntry> entries = new ArrayList<BundleEntry>();

        public List<BundleEntry> getEntries() {
            return entries;
        }
    }

    public static class BundleEntry {
        private String filename;
        private String value;

        public String getFilename() {
            return filename;
        }

        public void setFilename(String filename) {
            this.filename = filename;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

    public static void main(String[] args) throws IOException, SAXException, ParserConfigurationException {
        PropertiesIndex index = new PropertiesIndex();
        index.processDirectory("D:\\workspaces\\fo\\neoflex.fo.LoanApplication.Web");
        int l = 5;
    }
}
